﻿namespace Busconeitor.Domain.Utils.SimpleList
{
    using System;

    [Serializable]
    public class SimpleList
    {
        private Node _frente;
        private Node _actual;  // patrón Iterator: dirección del nodo que toca procesar.
        private int _longitud; // cantidad de nodos de la lista.

        /// <summary>
        /// Constructor por defecto
        /// </summary>
        public SimpleList()
        {
            _frente = null;
            _actual = null;
            _longitud = 0;
        }

        /// <summary>
        /// Retorna el número de elementos de la lista.
        /// </summary>
        /// <returns>el número de elementos que contiene la lista.</returns>
        public int Size()
        {
            return _longitud;
        }

        /**
         * Inserta un objeto al principio de la lista. La inserción se hará sólo si
         * el parámetro recibido no es null y si el objeto representado es
         * compatible con el contenido actual de la lista
         *
         * @param x el objeto a almacenar en la lista.
         */
        public void AddFirst(IComparable x)
        {
            if (!IsHomogeneus(x))
            {
                return;
            }

            // si llegó acá, está todo ok... inserte tranquilo
            var p = new Node(x, _frente);
            _frente = p;
            _longitud++;
        }

        /**
         * Inserta un objeto en forma ordenada en la lista. La inserción se hará
         * sólo si el parámetro recibido no es null y si el objeto representado es
         * compatible con el contenido actual de la lista. Se supone que la está ya
         * ordenada (es decir, se supone que todas las inserciones fueron realizadas
         * llamando a este método). Este método no viene en la clase LinkedList
         * tomada como modelo para el planteo realizado en clase de SimpleList: se
         * incorpora desde la materia TSB por tratarse de un algoritmo clásico e
         * interesante.
         *
         * @param x el objeto a almacenar en la lista.
         */
        public void AddInOrder(IComparable x)
        {
            if (!IsHomogeneus(x))
            {
                return;
            }

            // si llegó acá, está todo ok... inserte tranquilo
            var nuevo = new Node(x, null);
            var p = _frente;
            Node q = null;

            while (p != null && x.CompareTo(p.GetInfo()) >= 0)
            {
                q = p;
                p = p.GetNext();
            }

            nuevo.SetNext(p);

            if (q != null)
            {
                q.SetNext(nuevo);
            }
            else
            {
                _frente = nuevo;
            }

            _longitud++;
        }

        /**
         * Inserta un objeto al final de la lista. La inserción se hará sólo si el
         * parámetro recibido no es null y si el objeto representado es compatible
         * con el contenido actual de la lista
         *
         * @param x el objeto a almacenar en la lista.
         */
        public void AddLast(IComparable x)
        {
            if (!IsHomogeneus(x))
            {
                return;
            }

            // si llegó acá, está todo ok... inserte tranquilo
            Node nuevo = new Node(x, null);
            Node p = _frente, q = null;

            while (p != null)
            {
                q = p;
                p = p.GetNext();
            }
            
            if (q != null)
            {
                q.SetNext(nuevo);
            }
            else
            {
                _frente = nuevo;
            }
            
            _longitud++;
        }

        /**
         * Remueve todos los elementos de la lista.
         */
        public void Clear()
        {
            _frente = null; // ¿alguna duda?
            _actual = null;
            _longitud = 0;
        }

        /**
         * Determina si en la lista existe un elemento que coincida con x. Usamos
         * compareTo() para realizar las comparaciones (aunque podría usarse
         * equals()).
         *
         * @return true si x está en la lista - false si x no está o si x es null.
         * @param x el objeto a buscar.
         */
        public Boolean Contains(IComparable x)
        {
            if (!IsHomogeneus(x))
            {
                return false;
            }

            Node p = _frente;
            while (p != null && x.CompareTo(p.GetInfo()) != 0)
            {
                p = p.GetNext();
            }
            return (p != null);
        }

        /**
         * Retorna (pero sin removerlo) el objeto ubicado al principio de la lista.
         *
         * @return una referencia al primer elemento de la lista.
         * @throws NoSuchElementException si la lista estaba vacía.
         */
        public IComparable GetFirst()
        {
            if (_frente == null)
            {
                throw new Exception("Error: la lista está vacía...");
            }

            return _frente.GetInfo();
        }

        /**
         * Retorna (pero sin removerlo) el objeto ubicado al final de la lista.
         *
         * @return una referencia al primer elemento de la lista.
         * @throws NoSuchElementException si la lista estaba vacía.
         */
        public IComparable GetLast()
        {
            if (_frente == null)
            {
                throw new Exception("Error: la lista está vacía...");
            }

            Node p = _frente, q = null;
            while (p != null)
            {
                q = p;
                p = p.GetNext();
            }
            return (q != null) ? q.GetInfo() : _frente.GetInfo();
        }

        /**
         * Indica si queda algún objeto en el recorrido del iterador. Se incorpora
         * para cumplir nuestra implementación liviana del patrón Iterator.
         * Corresponde al método hasNext() de la clase Iterator del lenguaje Java.
         *
         * @return true si queda algún objeto en el recorrido - false si no quedan
         * objetos.
         */
        public Boolean HasNext()
        {
            if (_frente == null)
            {
                return false;
            }
            if (_actual != null && _actual.GetNext() == null)
            {
                return false;
            }
            return true;
        }

        /**
         * Retorna true si la lista está vacía.
         *
         * @return true si la lista está vacía - false en caso contrario.
         */
        public Boolean IsEmpty()
        {
            return (_frente == null);
        }

        /**
         * Retorna el siguiente objeto en el recorrido del iterador. Se incorpora
         * para cumplir nuestra implementación liviana del patrón Iterator.
         * Corresponde al método next() de la clase Iterator del lenguaje Java.
         *
         * @return el siguiente objeto en el recorrido.
         * @throws NoSuchElementException si la lista está vacía o en la lista no
         * quedan elementos por recorrer.
         */
        public IComparable Next()
        {
            if (!HasNext())
            {
                throw new Exception("No quedan elementos por recorrer");
            }

            if (_actual == null)
            {
                _actual = _frente;
            }
            else
            {
                _actual = _actual.GetNext();
            }
            return _actual.GetInfo();
        }

        /**
         * Remueve el objeto x de la lista. Retorna true si puede hacerlo, o false
         * en caso contrario.
         *
         * @param x el objeto que se desea eliminar de la lista.
         * @return true si la lista contenía a x y pudo eliminarlo.
         */
        public Boolean Remove(IComparable x)
        {
            if (!IsHomogeneus(x))
            {
                return false;
            }

            Node p = _frente, q = null;
            while (p != null && p.GetInfo() != x)
            {
                q = p;
                p = p.GetNext();
            }

            if (p != null)
            {
                if (q != null)
                {
                    q.SetNext(p.GetNext());
                }
                else
                {
                    _frente = p.GetNext();
                }
                _longitud--;
                return true;
            }

            return false;
        }

        /**
         * Retorna (y remueve) el objeto ubicado al final de la lista.
         *
         * @return el último elemento de la lista.
         * @throws NoSuchElementException si la lista estaba vacía.
         */
        public IComparable RemoveLast()
        {
            if (_frente == null)
            {
                throw new Exception("Error: la lista está vacía...");
            }

            Node p = _frente, q = null;
            while (p.GetNext() != null)
            {
                q = p;
                p = p.GetNext();
            }
            IComparable x = p.GetInfo();
            if (q != null)
            {
                q.SetNext(p.GetNext());
            }
            else
            {
                _frente = p.GetNext();
            }
            _longitud--;
            return x;
        }

        /**
         * Retorna (y remueve) el objeto ubicado al principio de la lista.
         *
         * @return el primer elemento de la lista.
         * @throws NoSuchElementException si la lista estaba vacía.
         */
        public IComparable RemoveFirst()
        {
            if (_frente == null)
            {
                throw new Exception("Error: la lista está vacía...");
            }

            IComparable x = _frente.GetInfo();
            _frente = _frente.GetNext();
            _longitud--;
            return x;
        }

        /**
         * Busca un objeto x en la lista, y en caso de encontrarlo retorna una
         * referencia al objeto que ESTÁ EN LA LISTA. Retorna null si x no está en
         * la lista o si x es null o si x no es compatible con el contenido de la
         * lista
         */
        public IComparable Search(IComparable x)
        {
            if (!IsHomogeneus(x))
            {
                return null;
            }

            IComparable r = null;
            Node p = _frente;
            while (p != null && x.CompareTo(p.GetInfo()) != 0)
            {
                p = p.GetNext();
            }
            if (p != null)
            {
                r = p.GetInfo();
            }
            return r;
        }

        /**
         * Inicializa el mecanismo de recorrido. Hace que la próxima invocación a
         * next() retorne el primer objeto de la lista. Forma parte de nuestra
         * implementación liviana del patrón Iterator. En la clase LinkedList de
         * Java, nuestro método sería equivalente a invocar al método iterator(), el
         * cual retorna un objeto de la clase Iterator (predefinida de Java).
         * Decidimos cambiar el nombre por razones de claridad.
         */
        public void StartIterator()
        {
            _actual = null;
        }

        /**
         * Redefine el método toString heredado desde Object.
         *
         * @return el contenido de la lista convertido a String.
         */
        public override string ToString()
        {
            Node p = _frente;
            String res = "";
            while (p != null)
            {
                res = res + p.ToString();
                if (p.GetNext() != null)
                {
                    res = res + " ";
                }
                p = p.GetNext();
            }
            return res;
        }

        // Este método controla que x sea homogeneo con el contenido de la lista
        // Retorna true si es homogéneo y false en caso contrario
        private Boolean IsHomogeneus(IComparable x)
        {
            if (x == null)
            {
                return false;
            }
            if (_frente != null && x.GetType() != _frente.GetInfo().GetType())
            {
                return false;
            }
            return true;
        }
    }
}
